package com.shoulder.boot.acl.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.shoulder.boot.acl.dto.*;
import com.shoulder.boot.acl.entity.*;
import com.shoulder.boot.acl.exception.AclException;
import com.shoulder.boot.acl.exception.RefreshException;
import com.shoulder.boot.acl.mapper.*;
import com.shoulder.boot.acl.security.SecurityUser;
import com.shoulder.boot.acl.service.UserService;
import com.shoulder.boot.acl.vo.AdminLoginVo;
import com.shoulder.boot.acl.vo.TenantUserVo;
import com.shoulder.boot.acl.vo.UserVo;
import com.shoulder.boot.acl.utils.UserUtils;
import com.shoulder.boot.system.entity.Dept;
import com.shoulder.boot.system.entity.Post;
import com.shoulder.boot.system.entity.Tenant;
import com.shoulder.boot.system.mapper.DeptMapper;
import com.shoulder.boot.system.mapper.PostMapper;
import com.shoulder.boot.system.mapper.TenantMapper;
import com.shoulder.core.enums.ScopeAuthEnums;
import com.shoulder.core.enums.StatusEnums;
import com.shoulder.core.exception.BizException;
import com.shoulder.core.fetch.BaseIds;
import com.shoulder.core.http.HttpRequest;
import com.shoulder.core.utils.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.core.env.Environment;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.*;
import java.util.stream.Collectors;


@Service
@Transactional
@AllArgsConstructor
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    private final UserRoleMapper userRoleMapper;
    private final RoleMenuMapper roleMenuMapper;
    private final Environment environment;
    private final RegisteredClientRepository registeredClientRepository;
    private final PasswordEncoder passwordEncoder;
    private final RoleMapper roleMapper;
    private final PostMapper postMapper;
    private final DeptMapper deptMapper;
    private final TenantMapper tenantMapper;
    private final DataScopeMapper dataScopeMapper;
    private final RedisTemplate<String, Object> redisTemplate;
    private final HttpRequest httpRequest;

    @Override
    public AdminLoginVo refresh(RefreshTokenDTO refreshTokenDTO) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (Strings.isNullOrEmpty(authorization)) {
            throw new AclException("Basic Auth请求头不能为空");
        }
        //解析 Authorization Basic Auth 请求头
        String clientInfo = new String(Base64.getDecoder().decode(authorization.replace("Basic ", "")));
        log.info("用户登录解析 Authorization Basic Auth 的客户端信息为:{}", clientInfo);
        //判断是否是正确的client
        String clientId = clientInfo.split(":")[0], clientSecret = clientInfo.split(":")[1];
        RegisteredClient client = registeredClientRepository.findByClientId(clientId);
        if (Objects.isNull(client)) {
            throw new AclException("客户端信息未在授权服务器注册");
        }
        if (!passwordEncoder.matches(clientSecret, client.getClientSecret())) {
            throw new AclException("客户端凭证不匹配");
        }

        HashMap<@Nullable String, @Nullable Object> objectHashMap = Maps.newHashMap();
        objectHashMap.put("grant_type", AuthorizationGrantType.REFRESH_TOKEN.getValue());
        objectHashMap.put(AuthorizationGrantType.REFRESH_TOKEN.getValue(), refreshTokenDTO.getRefreshToken());
        String body = httpRequest.post("http://localhost:" + environment.getProperty("local.server.port") + "/oauth2/token", String.class)
                .basicAuth(clientId, clientSecret)
                .formParams(objectHashMap)
                .header(LogTraceUtils.TRACE_ID, LogTraceUtils.get())
                .execute();
        return toVo(body, true);
    }

    @Override
    public AdminLoginVo login(AdminLoginDTO loginRequest) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (Strings.isNullOrEmpty(authorization)) {
            throw new AclException("Basic Auth请求头不能为空");
        }
        //解析 Authorization Basic Auth 请求头
        String clientInfo = new String(Base64.getDecoder().decode(authorization.replace("Basic ", "")));
        log.info("用户登录解析 Authorization Basic Auth 的客户端信息为:{}", clientInfo);
        //判断是否是正确的client
        String clientId = clientInfo.split(":")[0], clientSecret = clientInfo.split(":")[1];
        RegisteredClient client = registeredClientRepository.findByClientId(clientId);
        if (Objects.isNull(client)) {
            throw new AclException("客户端信息未在授权服务器注册");
        }
        if (!passwordEncoder.matches(clientSecret, client.getClientSecret())) {
            throw new AclException("客户端凭证不匹配");
        }
        Object object = redisTemplate.opsForValue().get(loginRequest.getUuid());
        if (object == null) {
            throw new AclException("验证码已过期");
        }
        String verifyCode = (String) object;
        if (!verifyCode.equalsIgnoreCase(loginRequest.getVerifyCode())) {
            throw new AclException("验证码不匹配");
        }
        HashMap<@Nullable String, @Nullable Object> objectHashMap = Maps.newHashMap();
        //todo 因为这里使用的是 密码 模式,就直接写死了 可以从 client.getAuthorizationGrantTypes() 获取
        objectHashMap.put("grant_type", "password");
        objectHashMap.put("username", loginRequest.getUsername());
        objectHashMap.put("password", loginRequest.getPassword());
        objectHashMap.put("scope", String.join(",", client.getScopes()));
        String body = httpRequest.post("http://localhost:" + environment.getProperty("local.server.port") + "/oauth2/token", String.class)
                .basicAuth(clientId, clientSecret)
                .header(LogTraceUtils.TRACE_ID, LogTraceUtils.get())
                .formParams(objectHashMap)
                .execute();
        redisTemplate.delete(loginRequest.getUuid());
        return toVo(body, false);
    }

    private AdminLoginVo toVo(String body, boolean refresh) {

        if (!JsonUtils.isTypeJSON(body)) {
            if (refresh) {
                throw new RefreshException("token刷新失败");
            }
            throw new AclException("获取token失败");
        }
        AdminLoginVo loginVo = MapUtils.underscoreToCamelCase(JsonUtils.json2Bean(body, Map.class), AdminLoginVo.class);
        if (Strings.isNullOrEmpty(loginVo.getAccessToken())) {
            if (refresh) {
                throw new RefreshException("token刷新失败");
            }
            throw new AclException("token获取失败");
        }
        return loginVo;
    }

    @Override
    public UserDetails loadUser(String username) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>().eq(User::getAccount, username);
        User user = this.baseMapper.selectOne(queryWrapper);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        Tenant tenant = tenantMapper.selectById(user.getTenantId());
        if (tenant == null || tenant.getStatus() != StatusEnums.active.getCode()) {
            throw new AclException("租户不存在或已禁用");
        }
        return buildSecurityUser(user);
    }

    private UserDetails buildSecurityUser(User user) {
        SecurityUser securityUser = new SecurityUser();
        BeanUtils.copyProperties(user, securityUser);
        List<Role> roles = userRoleMapper.findRolesByUserId(user.getId());
        if (!CollectionUtils.isEmpty(roles)) {
            List<String> list = new ArrayList<>(roles.stream().map(Role::getCode).filter(Objects::nonNull).distinct().toList());
            List<String> roleIds = roles.stream().map(Role::getId).collect(Collectors.toList());
            List<Menu> menus = roleMenuMapper.findMenuByRoleIds(roleIds);
            list.addAll(menus.stream().map(Menu::getCode).filter(Objects::nonNull).distinct().toList());
            securityUser.setAuths(list);
            //添加数据权限
            securityUser.setDataScopes(obtainDataScopes(roleIds, user));
        }
        return securityUser;
    }

    private List<String> obtainDataScopes(List<String> roleIds, User user) {
        List<String> dataScopes = new ArrayList<>();
        for (String roleId : roleIds) {
            Role role = this.roleMapper.selectById(roleId);
            if (ScopeAuthEnums.ALL.getScope().equals(role.getDataScope())) {
                dataScopes.addAll(deptMapper.selectList(new LambdaQueryWrapper<Dept>().eq(Dept::getTenantId, user.getTenantId())).stream().map(Dept::getId).toList());
            }
            if (ScopeAuthEnums.SELF_DEPT.getScope().equals(role.getDataScope())) {
                dataScopes.add(user.getDeptId());
            }
            if (ScopeAuthEnums.SELF_AND_CHILDREN.getScope().equals(role.getDataScope())) {
                List<Dept> selectList = deptMapper.selectList(new LambdaQueryWrapper<Dept>().eq(Dept::getTenantId, user.getTenantId()));
                Dept self = selectList.stream().filter(each -> each.getId().equals(user.getDeptId())).findFirst().orElse(new Dept());
                List<Dept> children = RecursionUtils.getChildren(MapUtils.bean2Map(self), MapUtils.bean2Map(selectList), new ArrayList<>(), Dept.class);
                if (!CollectionUtils.isEmpty(children)) {
                    dataScopes.addAll(children.stream().map(Dept::getId).toList());
                }
                dataScopes.add(user.getDeptId());
            }
            if (ScopeAuthEnums.ZDY.getScope().equals(role.getDataScope())) {
                List<DataScope> scopeList = dataScopeMapper.selectList(new LambdaQueryWrapper<DataScope>().eq(DataScope::getRoleId, roleId));
                if (!CollectionUtils.isEmpty(scopeList)) {
                    dataScopes.addAll(scopeList.stream().map(DataScope::getId).toList());
                }
            }
        }
        return dataScopes.stream().distinct().collect(Collectors.toList());
    }

    @Override
    public List<User> listQuery(UserQueryDTO queryDTO) {
        return this.baseMapper.selectList(UserUtils.buildQueryWrapper(queryDTO));
    }

    @Override
    public UserVo look(String id) {
        User entity = this.baseMapper.selectById(id);
        if (ObjectUtils.isEmpty(entity)) {
            throw new AclException("用户信息不存在");
        }
        return UserUtils.buildUtils(Collections.singletonList(entity), deptMapper, postMapper, tenantMapper).toVo(entity);
    }

    @Override
    public List<User> looks(BaseIds<String> baseIds) {
        return this.baseMapper.selectBatchIds(baseIds.getIds());
    }

    @Override
    public void del(String id) {
        this.userRoleMapper.delete(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, id));
        this.baseMapper.deleteById(id);
    }

    @Override
    public void batchDel(BaseIds<String> baseIds) {
        userRoleMapper.delete(new LambdaQueryWrapper<UserRole>().in(UserRole::getUserId, baseIds.getIds()));
        this.baseMapper.deleteBatchIds(baseIds.getIds());
    }


    @Override
    public Page<UserVo> pageQuery(UserQueryDTO queryDTO) {
        queryDTO.setTenantId(TokenUtils.parseUser().getTenantId());
        Page<User> selectPage = this.baseMapper.selectPage(new Page<>(queryDTO.getCurrent(), queryDTO.getSize())
                , UserUtils.buildQueryWrapper(queryDTO));
        if (selectPage == null || CollectionUtils.isEmpty(selectPage.getRecords())) {
            return new Page<>();
        }
        return UserUtils.buildUtils(selectPage.getRecords(), deptMapper, postMapper, tenantMapper).toPage(selectPage);
    }

    @Override
    public void mod(UserModDTO modDTO) {
        User info = Optional.ofNullable(this.baseMapper.selectById(modDTO.getId())).orElseThrow(() -> new AclException("用户信息不存在"));
        User user = this.baseMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getAccount, modDTO.getAccount()).ne(User::getId, info.getId()));

        if (user != null) {
            throw new BizException("用户账号已存在");
        }
        BeanUtils.copyProperties(modDTO, info);
        info.setPassword(null);
        this.baseMapper.updateById(info);
    }

    @Override
    public void add(UserAddDTO addDTO) {
        User info = this.baseMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getAccount, addDTO.getAccount()));
        if (info != null) {
            throw new BizException("用户账号已存在");
        }
        info = new User();
        BeanUtils.copyProperties(addDTO, info);
        info.setTenantId(TokenUtils.parseUser().getTenantId());
        info.setPassword(passwordEncoder.encode(addDTO.getPassword()));
        this.baseMapper.insert(info);
    }

    @Override
    public void r2u(R2u r2u) {
        this.userRoleMapper.delete(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, r2u.getUserId()));
        if (!CollectionUtils.isEmpty(r2u.getRoleIds())) {
            for (String roleId : r2u.getRoleIds()) {
                UserRole userRole = new UserRole();
                userRole.setRoleId(roleId);
                userRole.setUserId(r2u.getUserId());
                this.userRoleMapper.insert(userRole);
            }
        }
    }

    @Override
    public List<UserVo> forList(UserQueryDTO queryDTO) {
        queryDTO.setTenantId(TokenUtils.parseUser().getTenantId());
        List<User> users = this.baseMapper.selectList(UserUtils.buildQueryWrapper(queryDTO));
        return UserUtils.buildUtils(users, deptMapper, postMapper, tenantMapper).toVos(users);
    }

    @Override
    public TenantUserVo info() {
        String tenantId = TokenUtils.parseUser().getTenantId();
        TenantUserVo tenantUserVo = new TenantUserVo();
        tenantUserVo.setRoles(roleMapper.selectList(new LambdaQueryWrapper<Role>().eq(Role::getTenantId, tenantId)));
        tenantUserVo.setPosts(postMapper.selectList(new LambdaQueryWrapper<Post>().eq(Post::getTenantId, tenantId)));
        List<Dept> depts = deptMapper.selectList(new LambdaQueryWrapper<Dept>().eq(Dept::getTenantId, tenantId));
        tenantUserVo.setDeptList(TreeUtils.build(depts, Dept::getParentId, Dept::getId, e -> "0".equals(e.getParentId()), Dept::setChildren));
        return tenantUserVo;
    }

    @Override
    public void switchStatus(String id) {
        User user = this.baseMapper.selectById(id);
        if (ObjectUtils.isEmpty(user)) {
            throw new AclException("用户信息不存在");
        }
        user.setStatus(StatusEnums.active.getCode() == user.getStatus() ? StatusEnums.disabled.getCode() : StatusEnums.active.getCode());
        this.baseMapper.updateById(user);
    }


}
